/*!
 * jQuery Validation Plugin v1.17.0
 *
 * https://jqueryvalidation.org/
 *
 * Copyright (c) 2017 Jörn Zaefferer
 * Released under the MIT license
 */
(function (factory) {
  if (typeof define === "function" && define.amd) {
    define(["jquery"], factory);
  } else if (typeof module === "object" && module.exports) {
    module.exports = factory(require("jquery"));
  } else {
    factory(jQuery);
  }
}(function ($) {

  $.extend($.fn, {

    // https://jqueryvalidation.org/validate/
    validate: function (options) {

      // If nothing is selected, return nothing; can't chain anyway
      if (!this.length) {
        if (options && options.debug && window.console) {
          console.warn("Nothing selected, can't validate, returning nothing.");
        }
        return;
      }

      // Check if a validator for this form was already created
      var validator = $.data(this[0], "validator");
      if (validator) {
        return validator;
      }

      // Add novalidate tag if HTML5.
      this.attr("novalidate", "novalidate");

      validator = new $.validator(options, this[0]);
      $.data(this[0], "validator", validator);

      if (validator.settings.onsubmit) {

        this.on("click.validate", ":submit", function (event) {

          // Track the used submit button to properly handle scripted
          // submits later.
          validator.submitButton = event.currentTarget;

          // Allow suppressing validation by adding a cancel class to the submit button
          if ($(this).hasClass("cancel")) {
            validator.cancelSubmit = true;
          }

          // Allow suppressing validation by adding the html5 formnovalidate attribute to the submit button
          if ($(this).attr("formnovalidate") !== undefined) {
            validator.cancelSubmit = true;
          }
        });

        // Validate the form on submit
        this.on("submit.validate", function (event) {
          if (validator.settings.debug) {

            // Prevent form submit to be able to see console output
            event.preventDefault();
          }

          function handle() {
            var hidden, result;

            // Insert a hidden input as a replacement for the missing submit button
            // The hidden input is inserted in two cases:
            //   - A user defined a `submitHandler`
            //   - There was a pending request due to `remote` method and `stopRequest()`
            //     was called to submit the form in case it's valid
            if (validator.submitButton && (validator.settings.submitHandler || validator.formSubmitted)) {
              hidden = $("<input type='hidden'/>")
                .attr("name", validator.submitButton.name)
                .val($(validator.submitButton).val())
                .appendTo(validator.currentForm);
            }

            if (validator.settings.submitHandler) {
              result = validator.settings.submitHandler.call(validator, validator.currentForm, event);
              if (hidden) {

                // And clean up afterwards; thanks to no-block-scope, hidden can be referenced
                hidden.remove();
              }
              if (result !== undefined) {
                return result;
              }
              return false;
            }
            return true;
          }

          // Prevent submit for invalid forms or custom submit handlers
          if (validator.cancelSubmit) {
            validator.cancelSubmit = false;
            return handle();
          }
          if (validator.form()) {
            if (validator.pendingRequest) {
              validator.formSubmitted = true;
              return false;
            }
            return handle();
          } else {
            validator.focusInvalid();
            return false;
          }
        });
      }

      return validator;
    },

    // https://jqueryvalidation.org/valid/
    valid: function () {
      var valid, validator, errorList;

      if ($(this[0]).is("form")) {
        valid = this.validate().form();
      } else {
        errorList = [];
        valid = true;
        validator = $(this[0].form).validate();
        this.each(function () {
          valid = validator.element(this) && valid;
          if (!valid) {
            errorList = errorList.concat(validator.errorList);
          }
        });
        validator.errorList = errorList;
      }
      return valid;
    },

    // https://jqueryvalidation.org/rules/
    rules: function (command, argument) {
      var element = this[0],
        settings, staticRules, existingRules, data, param, filtered;

      // If nothing is selected, return empty object; can't chain anyway
      if (element == null) {
        return;
      }

      if (!element.form && element.hasAttribute("contenteditable")) {
        element.form = this.closest("form")[0];
        element.name = this.attr("name");
      }

      if (element.form == null) {
        return;
      }

      if (command) {
        settings = $.data(element.form, "validator").settings;
        staticRules = settings.rules;
        existingRules = $.validator.staticRules(element);
        switch (command) {
          case "add":
            $.extend(existingRules, $.validator.normalizeRule(argument));

            // Remove messages from rules, but allow them to be set separately
            delete existingRules.messages;
            staticRules[element.name] = existingRules;
            if (argument.messages) {
              settings.messages[element.name] = $.extend(settings.messages[element.name], argument.messages);
            }
            break;
          case "remove":
            if (!argument) {
              delete staticRules[element.name];
              return existingRules;
            }
            filtered = {};
            $.each(argument.split(/\s/), function (index, method) {
              filtered[method] = existingRules[method];
              delete existingRules[method];
            });
            return filtered;
        }
      }

      data = $.validator.normalizeRules(
        $.extend({},
          $.validator.classRules(element),
          $.validator.attributeRules(element),
          $.validator.dataRules(element),
          $.validator.staticRules(element)
        ), element);

      // Make sure required is at front
      if (data.required) {
        param = data.required;
        delete data.required;
        data = $.extend({
          required: param
        }, data);
      }

      // Make sure remote is at back
      if (data.remote) {
        param = data.remote;
        delete data.remote;
        data = $.extend(data, {
          remote: param
        });
      }

      return data;
    }
  });

  // Custom selectors
  $.extend($.expr.pseudos || $.expr[":"], { // '|| $.expr[ ":" ]' here enables backwards compatibility to jQuery 1.7. Can be removed when dropping jQ 1.7.x support

    // https://jqueryvalidation.org/blank-selector/
    blank: function (a) {
      return !$.trim("" + $(a).val());
    },

    // https://jqueryvalidation.org/filled-selector/
    filled: function (a) {
      var val = $(a).val();
      return val !== null && !!$.trim("" + val);
    },

    // https://jqueryvalidation.org/unchecked-selector/
    unchecked: function (a) {
      return !$(a).prop("checked");
    }
  });

  // Constructor for validator
  $.validator = function (options, form) {
    this.settings = $.extend(true, {}, $.validator.defaults, options);
    this.currentForm = form;
    this.init();
  };

  // https://jqueryvalidation.org/jQuery.validator.format/
  $.validator.format = function (source, params) {
    if (arguments.length === 1) {
      return function () {
        var args = $.makeArray(arguments);
        args.unshift(source);
        return $.validator.format.apply(this, args);
      };
    }
    if (params === undefined) {
      return source;
    }
    if (arguments.length > 2 && params.constructor !== Array) {
      params = $.makeArray(arguments).slice(1);
    }
    if (params.constructor !== Array) {
      params = [params];
    }
    $.each(params, function (i, n) {
      source = source.replace(new RegExp("\\{" + i + "\\}", "g"), function () {
        return n;
      });
    });
    return source;
  };

  $.extend($.validator, {

    defaults: {
      messages: {},
      groups: {},
      rules: {},
      errorClass: "error",
      pendingClass: "pending",
      validClass: "valid",
      errorElement: "label",
      focusCleanup: false,
      focusInvalid: true,
      errorContainer: $([]),
      errorLabelContainer: $([]),
      onsubmit: true,
      ignore: ":hidden",
      ignoreTitle: false,
      onfocusin: function (element) {
        this.lastActive = element;

        // Hide error label and remove error class on focus if enabled
        if (this.settings.focusCleanup) {
          if (this.settings.unhighlight) {
            this.settings.unhighlight.call(this, element, this.settings.errorClass, this.settings.validClass);
          }
          this.hideThese(this.errorsFor(element));
        }
      },
      onfocusout: function (element) {
        if (!this.checkable(element) && (element.name in this.submitted || !this.optional(element))) {
          this.element(element);
        }
      },
      onkeyup: function (element, event) {

        // Avoid revalidate the field when pressing one of the following keys
        // Shift       => 16
        // Ctrl        => 17
        // Alt         => 18
        // Caps lock   => 20
        // End         => 35
        // Home        => 36
        // Left arrow  => 37
        // Up arrow    => 38
        // Right arrow => 39
        // Down arrow  => 40
        // Insert      => 45
        // Num lock    => 144
        // AltGr key   => 225
        var excludedKeys = [
          16, 17, 18, 20, 35, 36, 37,
          38, 39, 40, 45, 144, 225
        ];

        if (event.which === 9 && this.elementValue(element) === "" || $.inArray(event.keyCode, excludedKeys) !== -1) {
          return;
        } else if (element.name in this.submitted || element.name in this.invalid) {
          this.element(element);
        }
      },
      onclick: function (element) {

        // Click on selects, radiobuttons and checkboxes
        if (element.name in this.submitted) {
          this.element(element);

          // Or option elements, check parent select in that case
        } else if (element.parentNode.name in this.submitted) {
          this.element(element.parentNode);
        }
      },
      highlight: function (element, errorClass, validClass) {
        if (element.type === "radio") {
          this.findByName(element.name).addClass(errorClass).removeClass(validClass);
        } else {
          $(element).addClass(errorClass).removeClass(validClass);
        }
      },
      unhighlight: function (element, errorClass, validClass) {
        if (element.type === "radio") {
          this.findByName(element.name).removeClass(errorClass).addClass(validClass);
        } else {
          $(element).removeClass(errorClass).addClass(validClass);
        }
      }
    },

    // https://jqueryvalidation.org/jQuery.validator.setDefaults/
    setDefaults: function (settings) {
      $.extend($.validator.defaults, settings);
    },

    messages: {
      required: "This field is required.",
      remote: "Please fix this field.",
      email: "Please enter a valid email address.",
      url: "Please enter a valid URL.",
      date: "Please enter a valid date.",
      dateISO: "Please enter a valid date (ISO).",
      number: "Please enter a valid number.",
      digits: "Please enter only digits.",
      equalTo: "Please enter the same value again.",
      maxlength: $.validator.format("Please enter no more than {0} characters."),
      minlength: $.validator.format("Please enter at least {0} characters."),
      rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
      range: $.validator.format("Please enter a value between {0} and {1}."),
      max: $.validator.format("Please enter a value less than or equal to {0}."),
      min: $.validator.format("Please enter a value greater than or equal to {0}."),
      step: $.validator.format("Please enter a multiple of {0}.")
    },

    autoCreateRanges: false,

    prototype: {

      init: function () {
        this.labelContainer = $(this.settings.errorLabelContainer);
        this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
        this.containers = $(this.settings.errorContainer).add(this.settings.errorLabelContainer);
        this.submitted = {};
        this.valueCache = {};
        this.pendingRequest = 0;
        this.pending = {};
        this.invalid = {};
        this.reset();

        var groups = (this.groups = {}),
          rules;
        $.each(this.settings.groups, function (key, value) {
          if (typeof value === "string") {
            value = value.split(/\s/);
          }
          $.each(value, function (index, name) {
            groups[name] = key;
          });
        });
        rules = this.settings.rules;
        $.each(rules, function (key, value) {
          rules[key] = $.validator.normalizeRule(value);
        });

        function delegate(event) {

          // Set form expando on contenteditable
          if (!this.form && this.hasAttribute("contenteditable")) {
            this.form = $(this).closest("form")[0];
            this.name = $(this).attr("name");
          }

          var validator = $.data(this.form, "validator"),
            eventType = "on" + event.type.replace(/^validate/, ""),
            settings = validator.settings;
          if (settings[eventType] && !$(this).is(settings.ignore)) {
            settings[eventType].call(validator, this, event);
          }
        }

        $(this.currentForm)
          .on("focusin.validate focusout.validate keyup.validate",
            ":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], " +
            "[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " +
            "[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " +
            "[type='radio'], [type='checkbox'], [contenteditable], [type='button']", delegate)

          // Support: Chrome, oldIE
          // "select" is provided as event.target when clicking a option
          .on("click.validate", "select, option, [type='radio'], [type='checkbox']", delegate);

        if (this.settings.invalidHandler) {
          $(this.currentForm).on("invalid-form.validate", this.settings.invalidHandler);
        }
      },

      // https://jqueryvalidation.org/Validator.form/
      form: function () {
        this.checkForm();
        $.extend(this.submitted, this.errorMap);
        this.invalid = $.extend({}, this.errorMap);
        if (!this.valid()) {
          $(this.currentForm).triggerHandler("invalid-form", [this]);
        }
        this.showErrors();
        return this.valid();
      },

      checkForm: function () {
        this.prepareForm();
        for (var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++) {
          this.check(elements[i]);
        }
        return this.valid();
      },

      // https://jqueryvalidation.org/Validator.element/
      element: function (element) {
        var cleanElement = this.clean(element),
          checkElement = this.validationTargetFor(cleanElement),
          v = this,
          result = true,
          rs, group;

        if (checkElement === undefined) {
          delete this.invalid[cleanElement.name];
        } else {
          this.prepareElement(checkElement);
          this.currentElements = $(checkElement);

          // If this element is grouped, then validate all group elements already
          // containing a value
          group = this.groups[checkElement.name];
          if (group) {
            $.each(this.groups, function (name, testgroup) {
              if (testgroup === group && name !== checkElement.name) {
                cleanElement = v.validationTargetFor(v.clean(v.findByName(name)));
                if (cleanElement && cleanElement.name in v.invalid) {
                  v.currentElements.push(cleanElement);
                  result = v.check(cleanElement) && result;
                }
              }
            });
          }

          rs = this.check(checkElement) !== false;
          result = result && rs;
          if (rs) {
            this.invalid[checkElement.name] = false;
          } else {
            this.invalid[checkElement.name] = true;
          }

          if (!this.numberOfInvalids()) {

            // Hide error containers on last error
            this.toHide = this.toHide.add(this.containers);
          }
          this.showErrors();

          // Add aria-invalid status for screen readers
          $(element).attr("aria-invalid", !rs);
        }

        return result;
      },

      // https://jqueryvalidation.org/Validator.showErrors/
      showErrors: function (errors) {
        if (errors) {
          var validator = this;

          // Add items to error list and map
          $.extend(this.errorMap, errors);
          this.errorList = $.map(this.errorMap, function (message, name) {
            return {
              message: message,
              element: validator.findByName(name)[0]
            };
          });

          // Remove items from success list
          this.successList = $.grep(this.successList, function (element) {
            return !(element.name in errors);
          });
        }
        if (this.settings.showErrors) {
          this.settings.showErrors.call(this, this.errorMap, this.errorList);
        } else {
          this.defaultShowErrors();
        }
      },

      // https://jqueryvalidation.org/Validator.resetForm/
      resetForm: function () {
        if ($.fn.resetForm) {
          $(this.currentForm).resetForm();
        }
        this.invalid = {};
        this.submitted = {};
        this.prepareForm();
        this.hideErrors();
        var elements = this.elements()
          .removeData("previousValue")
          .removeAttr("aria-invalid");

        this.resetElements(elements);
      },

      resetElements: function (elements) {
        var i;

        if (this.settings.unhighlight) {
          for (i = 0; elements[i]; i++) {
            this.settings.unhighlight.call(this, elements[i],
              this.settings.errorClass, "");
            this.findByName(elements[i].name).removeClass(this.settings.validClass);
          }
        } else {
          elements
            .removeClass(this.settings.errorClass)
            .removeClass(this.settings.validClass);
        }
      },

      numberOfInvalids: function () {
        return this.objectLength(this.invalid);
      },

      objectLength: function (obj) {
        /* jshint unused: false */
        var count = 0,
          i;
        for (i in obj) {

          // This check allows counting elements with empty error
          // message as invalid elements
          if (obj[i] !== undefined && obj[i] !== null && obj[i] !== false) {
            count++;
          }
        }
        return count;
      },

      hideErrors: function () {
        this.hideThese(this.toHide);
      },

      hideThese: function (errors) {
        errors.not(this.containers).text("");
        this.addWrapper(errors).hide();
      },

      valid: function () {
        return this.size() === 0;
      },

      size: function () {
        return this.errorList.length;
      },

      focusInvalid: function () {
        if (this.settings.focusInvalid) {
          try {
            $(this.findLastActive() || this.errorList.length && this.errorList[0].element || [])
              .filter(":visible")
              .focus()

              // Manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
              .trigger("focusin");
          } catch (e) {

            // Ignore IE throwing errors when focusing hidden elements
          }
        }
      },

      findLastActive: function () {
        var lastActive = this.lastActive;
        return lastActive && $.grep(this.errorList, function (n) {
          return n.element.name === lastActive.name;
        }).length === 1 && lastActive;
      },

      elements: function () {
        var validator = this,
          rulesCache = {};

        // Select all valid inputs inside the form (no submit or reset buttons)
        return $(this.currentForm)
          .find("input, select, textarea, [contenteditable]")
          .not(":submit, :reset, :image, :disabled")
          .not(this.settings.ignore)
          .filter(function () {
            var name = this.name || $(this).attr("name"); // For contenteditable
            if (!name && validator.settings.debug && window.console) {
              console.error("%o has no name assigned", this);
            }

            // Set form expando on contenteditable
            if (this.hasAttribute("contenteditable")) {
              this.form = $(this).closest("form")[0];
              this.name = name;
            }

            // Select only the first element for each name, and only those with rules specified
            if (name in rulesCache || !validator.objectLength($(this).rules())) {
              return false;
            }

            rulesCache[name] = true;
            return true;
          });
      },

      clean: function (selector) {
        return $(selector)[0];
      },

      errors: function () {
        var errorClass = this.settings.errorClass.split(" ").join(".");
        return $(this.settings.errorElement + "." + errorClass, this.errorContext);
      },

      resetInternals: function () {
        this.successList = [];
        this.errorList = [];
        this.errorMap = {};
        this.toShow = $([]);
        this.toHide = $([]);
      },

      reset: function () {
        this.resetInternals();
        this.currentElements = $([]);
      },

      prepareForm: function () {
        this.reset();
        this.toHide = this.errors().add(this.containers);
      },

      prepareElement: function (element) {
        this.reset();
        this.toHide = this.errorsFor(element);
      },

      elementValue: function (element) {
        var $element = $(element),
          type = element.type,
          val, idx;

        if (type === "radio" || type === "checkbox") {
          return this.findByName(element.name).filter(":checked").val();
        } else if (type === "number" && typeof element.validity !== "undefined") {
          return element.validity.badInput ? "NaN" : $element.val();
        }

        if (element.hasAttribute("contenteditable")) {
          val = $element.text();
        } else {
          val = $element.val();
        }

        if (type === "file") {

          // Modern browser (chrome & safari)
          if (val.substr(0, 12) === "C:\\fakepath\\") {
            return val.substr(12);
          }

          // Legacy browsers
          // Unix-based path
          idx = val.lastIndexOf("/");
          if (idx >= 0) {
            return val.substr(idx + 1);
          }

          // Windows-based path
          idx = val.lastIndexOf("\\");
          if (idx >= 0) {
            return val.substr(idx + 1);
          }

          // Just the file name
          return val;
        }

        if (typeof val === "string") {
          return val.replace(/\r/g, "");
        }
        return val;
      },

      check: function (element) {
        element = this.validationTargetFor(this.clean(element));

        var rules = $(element).rules(),
          rulesCount = $.map(rules, function (n, i) {
            return i;
          }).length,
          dependencyMismatch = false,
          val = this.elementValue(element),
          result, method, rule, normalizer;

        // Prioritize the local normalizer defined for this element over the global one
        // if the former exists, otherwise user the global one in case it exists.
        if (typeof rules.normalizer === "function") {
          normalizer = rules.normalizer;
        } else if (typeof this.settings.normalizer === "function") {
          normalizer = this.settings.normalizer;
        }

        // If normalizer is defined, then call it to retreive the changed value instead
        // of using the real one.
        // Note that `this` in the normalizer is `element`.
        if (normalizer) {
          val = normalizer.call(element, val);

          if (typeof val !== "string") {
            throw new TypeError("The normalizer should return a string value.");
          }

          // Delete the normalizer from rules to avoid treating it as a pre-defined method.
          delete rules.normalizer;
        }

        for (method in rules) {
          rule = {
            method: method,
            parameters: rules[method]
          };
          try {
            result = $.validator.methods[method].call(this, val, element, rule.parameters);

            // If a method indicates that the field is optional and therefore valid,
            // don't mark it as valid when there are no other rules
            if (result === "dependency-mismatch" && rulesCount === 1) {
              dependencyMismatch = true;
              continue;
            }
            dependencyMismatch = false;

            if (result === "pending") {
              this.toHide = this.toHide.not(this.errorsFor(element));
              return;
            }

            if (!result) {
              this.formatAndAdd(element, rule);
              return false;
            }
          } catch (e) {
            if (this.settings.debug && window.console) {
              console.log("Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e);
            }
            if (e instanceof TypeError) {
              e.message += ".  Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.";
            }

            throw e;
          }
        }
        if (dependencyMismatch) {
          return;
        }
        if (this.objectLength(rules)) {
          this.successList.push(element);
        }
        return true;
      },

      // Return the custom message for the given element and validation method
      // specified in the element's HTML5 data attribute
      // return the generic message if present and no method specific message is present
      customDataMessage: function (element, method) {
        return $(element).data("msg" + method.charAt(0).toUpperCase() +
          method.substring(1).toLowerCase()) || $(element).data("msg");
      },

      // Return the custom message for the given element name and validation method
      customMessage: function (name, method) {
        var m = this.settings.messages[name];
        return m && (m.constructor === String ? m : m[method]);
      },

      // Return the first defined argument, allowing empty strings
      findDefined: function () {
        for (var i = 0; i < arguments.length; i++) {
          if (arguments[i] !== undefined) {
            return arguments[i];
          }
        }
        return undefined;
      },

      // The second parameter 'rule' used to be a string, and extended to an object literal
      // of the following form:
      // rule = {
      //     method: "method name",
      //     parameters: "the given method parameters"
      // }
      //
      // The old behavior still supported, kept to maintain backward compatibility with
      // old code, and will be removed in the next major release.
      defaultMessage: function (element, rule) {
        if (typeof rule === "string") {
          rule = {
            method: rule
          };
        }

        var message = this.findDefined(
            this.customMessage(element.name, rule.method),
            this.customDataMessage(element, rule.method),

            // 'title' is never undefined, so handle empty string as undefined
            !this.settings.ignoreTitle && element.title || undefined,
            $.validator.messages[rule.method],
            "<strong>Warning: No message defined for " + element.name + "</strong>"
          ),
          theregex = /\$?\{(\d+)\}/g;
        if (typeof message === "function") {
          message = message.call(this, rule.parameters, element);
        } else if (theregex.test(message)) {
          message = $.validator.format(message.replace(theregex, "{$1}"), rule.parameters);
        }

        return message;
      },

      formatAndAdd: function (element, rule) {
        var message = this.defaultMessage(element, rule);

        this.errorList.push({
          message: message,
          element: element,
          method: rule.method
        });

        this.errorMap[element.name] = message;
        this.submitted[element.name] = message;
      },

      addWrapper: function (toToggle) {
        if (this.settings.wrapper) {
          toToggle = toToggle.add(toToggle.parent(this.settings.wrapper));
        }
        return toToggle;
      },

      defaultShowErrors: function () {
        var i, elements, error;
        for (i = 0; this.errorList[i]; i++) {
          error = this.errorList[i];
          if (this.settings.highlight) {
            this.settings.highlight.call(this, error.element, this.settings.errorClass, this.settings.validClass);
          }
          this.showLabel(error.element, error.message);
        }
        if (this.errorList.length) {
          this.toShow = this.toShow.add(this.containers);
        }
        if (this.settings.success) {
          for (i = 0; this.successList[i]; i++) {
            this.showLabel(this.successList[i]);
          }
        }
        if (this.settings.unhighlight) {
          for (i = 0, elements = this.validElements(); elements[i]; i++) {
            this.settings.unhighlight.call(this, elements[i], this.settings.errorClass, this.settings.validClass);
          }
        }
        this.toHide = this.toHide.not(this.toShow);
        this.hideErrors();
        this.addWrapper(this.toShow).show();
      },

      validElements: function () {
        return this.currentElements.not(this.invalidElements());
      },

      invalidElements: function () {
        return $(this.errorList).map(function () {
          return this.element;
        });
      },

      showLabel: function (element, message) {
        var place, group, errorID, v,
          error = this.errorsFor(element),
          elementID = this.idOrName(element),
          describedBy = $(element).attr("aria-describedby");

        if (error.length) {

          // Refresh error/success class
          error.removeClass(this.settings.validClass).addClass(this.settings.errorClass);

          // Replace message on existing label
          error.html(message);
        } else {

          // Create error element
          error = $("<" + this.settings.errorElement + ">")
            .attr("id", elementID + "-error")
            .addClass(this.settings.errorClass)
            .html(message || "");

          // Maintain reference to the element to be placed into the DOM
          place = error;
          if (this.settings.wrapper) {

            // Make sure the element is visible, even in IE
            // actually showing the wrapped element is handled elsewhere
            place = error.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
          }
          if (this.labelContainer.length) {
            this.labelContainer.append(place);
          } else if (this.settings.errorPlacement) {
            this.settings.errorPlacement.call(this, place, $(element));
          } else {
            place.insertAfter(element);
          }

          // Link error back to the element
          if (error.is("label")) {

            // If the error is a label, then associate using 'for'
            error.attr("for", elementID);

            // If the element is not a child of an associated label, then it's necessary
            // to explicitly apply aria-describedby
          } else if (error.parents("label[for='" + this.escapeCssMeta(elementID) + "']").length === 0) {
            errorID = error.attr("id");

            // Respect existing non-error aria-describedby
            if (!describedBy) {
              describedBy = errorID;
            } else if (!describedBy.match(new RegExp("\\b" + this.escapeCssMeta(errorID) + "\\b"))) {

              // Add to end of list if not already present
              describedBy += " " + errorID;
            }
            $(element).attr("aria-describedby", describedBy);

            // If this element is grouped, then assign to all elements in the same group
            group = this.groups[element.name];
            if (group) {
              v = this;
              $.each(v.groups, function (name, testgroup) {
                if (testgroup === group) {
                  $("[name='" + v.escapeCssMeta(name) + "']", v.currentForm)
                    .attr("aria-describedby", error.attr("id"));
                }
              });
            }
          }
        }
        if (!message && this.settings.success) {
          error.text("");
          if (typeof this.settings.success === "string") {
            error.addClass(this.settings.success);
          } else {
            this.settings.success(error, element);
          }
        }
        this.toShow = this.toShow.add(error);
      },

      errorsFor: function (element) {
        var name = this.escapeCssMeta(this.idOrName(element)),
          describer = $(element).attr("aria-describedby"),
          selector = "label[for='" + name + "'], label[for='" + name + "'] *";

        // 'aria-describedby' should directly reference the error element
        if (describer) {
          selector = selector + ", #" + this.escapeCssMeta(describer)
            .replace(/\s+/g, ", #");
        }

        return this
          .errors()
          .filter(selector);
      },

      // See https://api.jquery.com/category/selectors/, for CSS
      // meta-characters that should be escaped in order to be used with JQuery
      // as a literal part of a name/id or any selector.
      escapeCssMeta: function (string) {
        return string.replace(/([\\!"#$%&'()*+,./:;<=>?@\[\]^`{|}~])/g, "\\$1");
      },

      idOrName: function (element) {
        return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
      },

      validationTargetFor: function (element) {

        // If radio/checkbox, validate first element in group instead
        if (this.checkable(element)) {
          element = this.findByName(element.name);
        }

        // Always apply ignore filter
        return $(element).not(this.settings.ignore)[0];
      },

      checkable: function (element) {
        return (/radio|checkbox/i).test(element.type);
      },

      findByName: function (name) {
        return $(this.currentForm).find("[name='" + this.escapeCssMeta(name) + "']");
      },

      getLength: function (value, element) {
        switch (element.nodeName.toLowerCase()) {
          case "select":
            return $("option:selected", element).length;
          case "input":
            if (this.checkable(element)) {
              return this.findByName(element.name).filter(":checked").length;
            }
        }
        return value.length;
      },

      depend: function (param, element) {
        return this.dependTypes[typeof param] ? this.dependTypes[typeof param](param, element) : true;
      },

      dependTypes: {
        "boolean": function (param) {
          return param;
        },
        "string": function (param, element) {
          return !!$(param, element.form).length;
        },
        "function": function (param, element) {
          return param(element);
        }
      },

      optional: function (element) {
        var val = this.elementValue(element);
        return !$.validator.methods.required.call(this, val, element) && "dependency-mismatch";
      },

      startRequest: function (element) {
        if (!this.pending[element.name]) {
          this.pendingRequest++;
          $(element).addClass(this.settings.pendingClass);
          this.pending[element.name] = true;
        }
      },

      stopRequest: function (element, valid) {
        this.pendingRequest--;

        // Sometimes synchronization fails, make sure pendingRequest is never < 0
        if (this.pendingRequest < 0) {
          this.pendingRequest = 0;
        }
        delete this.pending[element.name];
        $(element).removeClass(this.settings.pendingClass);
        if (valid && this.pendingRequest === 0 && this.formSubmitted && this.form()) {
          $(this.currentForm).submit();

          // Remove the hidden input that was used as a replacement for the
          // missing submit button. The hidden input is added by `handle()`
          // to ensure that the value of the used submit button is passed on
          // for scripted submits triggered by this method
          if (this.submitButton) {
            $("input:hidden[name='" + this.submitButton.name + "']", this.currentForm).remove();
          }

          this.formSubmitted = false;
        } else if (!valid && this.pendingRequest === 0 && this.formSubmitted) {
          $(this.currentForm).triggerHandler("invalid-form", [this]);
          this.formSubmitted = false;
        }
      },

      previousValue: function (element, method) {
        method = typeof method === "string" && method || "remote";

        return $.data(element, "previousValue") || $.data(element, "previousValue", {
          old: null,
          valid: true,
          message: this.defaultMessage(element, {
            method: method
          })
        });
      },

      // Cleans up all forms and elements, removes validator-specific events
      destroy: function () {
        this.resetForm();

        $(this.currentForm)
          .off(".validate")
          .removeData("validator")
          .find(".validate-equalTo-blur")
          .off(".validate-equalTo")
          .removeClass("validate-equalTo-blur");
      }

    },

    classRuleSettings: {
      required: {
        required: true
      },
      email: {
        email: true
      },
      url: {
        url: true
      },
      date: {
        date: true
      },
      dateISO: {
        dateISO: true
      },
      number: {
        number: true
      },
      digits: {
        digits: true
      },
      creditcard: {
        creditcard: true
      }
    },

    addClassRules: function (className, rules) {
      if (className.constructor === String) {
        this.classRuleSettings[className] = rules;
      } else {
        $.extend(this.classRuleSettings, className);
      }
    },

    classRules: function (element) {
      var rules = {},
        classes = $(element).attr("class");

      if (classes) {
        $.each(classes.split(" "), function () {
          if (this in $.validator.classRuleSettings) {
            $.extend(rules, $.validator.classRuleSettings[this]);
          }
        });
      }
      return rules;
    },

    normalizeAttributeRule: function (rules, type, method, value) {

      // Convert the value to a number for number inputs, and for text for backwards compability
      // allows type="date" and others to be compared as strings
      if (/min|max|step/.test(method) && (type === null || /number|range|text/.test(type))) {
        value = Number(value);

        // Support Opera Mini, which returns NaN for undefined minlength
        if (isNaN(value)) {
          value = undefined;
        }
      }

      if (value || value === 0) {
        rules[method] = value;
      } else if (type === method && type !== "range") {

        // Exception: the jquery validate 'range' method
        // does not test for the html5 'range' type
        rules[method] = true;
      }
    },

    attributeRules: function (element) {
      var rules = {},
        $element = $(element),
        type = element.getAttribute("type"),
        method, value;

      for (method in $.validator.methods) {

        // Support for <input required> in both html5 and older browsers
        if (method === "required") {
          value = element.getAttribute(method);

          // Some browsers return an empty string for the required attribute
          // and non-HTML5 browsers might have required="" markup
          if (value === "") {
            value = true;
          }

          // Force non-HTML5 browsers to return bool
          value = !!value;
        } else {
          value = $element.attr(method);
        }

        this.normalizeAttributeRule(rules, type, method, value);
      }

      // 'maxlength' may be returned as -1, 2147483647 ( IE ) and 524288 ( safari ) for text inputs
      if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) {
        delete rules.maxlength;
      }

      return rules;
    },

    dataRules: function (element) {
      var rules = {},
        $element = $(element),
        type = element.getAttribute("type"),
        method, value;

      for (method in $.validator.methods) {
        value = $element.data("rule" + method.charAt(0).toUpperCase() + method.substring(1).toLowerCase());
        this.normalizeAttributeRule(rules, type, method, value);
      }
      return rules;
    },

    staticRules: function (element) {
      var rules = {},
        validator = $.data(element.form, "validator");

      if (validator.settings.rules) {
        rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {};
      }
      return rules;
    },

    normalizeRules: function (rules, element) {

      // Handle dependency check
      $.each(rules, function (prop, val) {

        // Ignore rule when param is explicitly false, eg. required:false
        if (val === false) {
          delete rules[prop];
          return;
        }
        if (val.param || val.depends) {
          var keepRule = true;
          switch (typeof val.depends) {
            case "string":
              keepRule = !!$(val.depends, element.form).length;
              break;
            case "function":
              keepRule = val.depends.call(element, element);
              break;
          }
          if (keepRule) {
            rules[prop] = val.param !== undefined ? val.param : true;
          } else {
            $.data(element.form, "validator").resetElements($(element));
            delete rules[prop];
          }
        }
      });

      // Evaluate parameters
      $.each(rules, function (rule, parameter) {
        rules[rule] = $.isFunction(parameter) && rule !== "normalizer" ? parameter(element) : parameter;
      });

      // Clean number parameters
      $.each(["minlength", "maxlength"], function () {
        if (rules[this]) {
          rules[this] = Number(rules[this]);
        }
      });
      $.each(["rangelength", "range"], function () {
        var parts;
        if (rules[this]) {
          if ($.isArray(rules[this])) {
            rules[this] = [Number(rules[this][0]), Number(rules[this][1])];
          } else if (typeof rules[this] === "string") {
            parts = rules[this].replace(/[\[\]]/g, "").split(/[\s,]+/);
            rules[this] = [Number(parts[0]), Number(parts[1])];
          }
        }
      });

      if ($.validator.autoCreateRanges) {

        // Auto-create ranges
        if (rules.min != null && rules.max != null) {
          rules.range = [rules.min, rules.max];
          delete rules.min;
          delete rules.max;
        }
        if (rules.minlength != null && rules.maxlength != null) {
          rules.rangelength = [rules.minlength, rules.maxlength];
          delete rules.minlength;
          delete rules.maxlength;
        }
      }

      return rules;
    },

    // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true}
    normalizeRule: function (data) {
      if (typeof data === "string") {
        var transformed = {};
        $.each(data.split(/\s/), function () {
          transformed[this] = true;
        });
        data = transformed;
      }
      return data;
    },

    // https://jqueryvalidation.org/jQuery.validator.addMethod/
    addMethod: function (name, method, message) {
      $.validator.methods[name] = method;
      $.validator.messages[name] = message !== undefined ? message : $.validator.messages[name];
      if (method.length < 3) {
        $.validator.addClassRules(name, $.validator.normalizeRule(name));
      }
    },

    // https://jqueryvalidation.org/jQuery.validator.methods/
    methods: {

      // https://jqueryvalidation.org/required-method/
      required: function (value, element, param) {

        // Check if dependency is met
        if (!this.depend(param, element)) {
          return "dependency-mismatch";
        }
        if (element.nodeName.toLowerCase() === "select") {

          // Could be an array for select-multiple or a string, both are fine this way
          var val = $(element).val();
          return val && val.length > 0;
        }
        if (this.checkable(element)) {
          return this.getLength(value, element) > 0;
        }
        return value.length > 0;
      },

      // https://jqueryvalidation.org/email-method/
      email: function (value, element) {

        // From https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
        // Retrieved 2014-01-14
        // If you have a problem with this implementation, report a bug against the above spec
        // Or use custom methods to implement your own email validation
        return this.optional(element) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(value);
      },

      // https://jqueryvalidation.org/url-method/
      url: function (value, element) {

        // Copyright (c) 2010-2013 Diego Perini, MIT licensed
        // https://gist.github.com/dperini/729294
        // see also https://mathiasbynens.be/demo/url-regex
        // modified to allow protocol-relative URLs
        return this.optional(element) || /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(value);
      },

      // https://jqueryvalidation.org/date-method/
      date: function (value, element) {
        return this.optional(element) || !/Invalid|NaN/.test(new Date(value).toString());
      },

      // https://jqueryvalidation.org/dateISO-method/
      dateISO: function (value, element) {
        return this.optional(element) || /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value);
      },

      // https://jqueryvalidation.org/number-method/
      number: function (value, element) {
        return this.optional(element) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value);
      },

      // https://jqueryvalidation.org/digits-method/
      digits: function (value, element) {
        return this.optional(element) || /^\d+$/.test(value);
      },

      // https://jqueryvalidation.org/minlength-method/
      minlength: function (value, element, param) {
        var length = $.isArray(value) ? value.length : this.getLength(value, element);
        return this.optional(element) || length >= param;
      },

      // https://jqueryvalidation.org/maxlength-method/
      maxlength: function (value, element, param) {
        var length = $.isArray(value) ? value.length : this.getLength(value, element);
        return this.optional(element) || length <= param;
      },

      // https://jqueryvalidation.org/rangelength-method/
      rangelength: function (value, element, param) {
        var length = $.isArray(value) ? value.length : this.getLength(value, element);
        return this.optional(element) || (length >= param[0] && length <= param[1]);
      },

      // https://jqueryvalidation.org/min-method/
      min: function (value, element, param) {
        return this.optional(element) || value >= param;
      },

      // https://jqueryvalidation.org/max-method/
      max: function (value, element, param) {
        return this.optional(element) || value <= param;
      },

      // https://jqueryvalidation.org/range-method/
      range: function (value, element, param) {
        return this.optional(element) || (value >= param[0] && value <= param[1]);
      },

      // https://jqueryvalidation.org/step-method/
      step: function (value, element, param) {
        var type = $(element).attr("type"),
          errorMessage = "Step attribute on input type " + type + " is not supported.",
          supportedTypes = ["text", "number", "range"],
          re = new RegExp("\\b" + type + "\\b"),
          notSupported = type && !re.test(supportedTypes.join()),
          decimalPlaces = function (num) {
            var match = ("" + num).match(/(?:\.(\d+))?$/);
            if (!match) {
              return 0;
            }

            // Number of digits right of decimal point.
            return match[1] ? match[1].length : 0;
          },
          toInt = function (num) {
            return Math.round(num * Math.pow(10, decimals));
          },
          valid = true,
          decimals;

        // Works only for text, number and range input types
        // TODO find a way to support input types date, datetime, datetime-local, month, time and week
        if (notSupported) {
          throw new Error(errorMessage);
        }

        decimals = decimalPlaces(param);

        // Value can't have too many decimals
        if (decimalPlaces(value) > decimals || toInt(value) % toInt(param) !== 0) {
          valid = false;
        }

        return this.optional(element) || valid;
      },

      // https://jqueryvalidation.org/equalTo-method/
      equalTo: function (value, element, param) {

        // Bind to the blur event of the target in order to revalidate whenever the target field is updated
        var target = $(param);
        if (this.settings.onfocusout && target.not(".validate-equalTo-blur").length) {
          target.addClass("validate-equalTo-blur").on("blur.validate-equalTo", function () {
            $(element).valid();
          });
        }
        return value === target.val();
      },

      // https://jqueryvalidation.org/remote-method/
      remote: function (value, element, param, method) {
        if (this.optional(element)) {
          return "dependency-mismatch";
        }

        method = typeof method === "string" && method || "remote";

        var previous = this.previousValue(element, method),
          validator, data, optionDataString;

        if (!this.settings.messages[element.name]) {
          this.settings.messages[element.name] = {};
        }
        previous.originalMessage = previous.originalMessage || this.settings.messages[element.name][method];
        this.settings.messages[element.name][method] = previous.message;

        param = typeof param === "string" && {
          url: param
        } || param;
        optionDataString = $.param($.extend({
          data: value
        }, param.data));
        if (previous.old === optionDataString) {
          return previous.valid;
        }

        previous.old = optionDataString;
        validator = this;
        this.startRequest(element);
        data = {};
        data[element.name] = value;
        $.ajax($.extend(true, {
          mode: "abort",
          port: "validate" + element.name,
          dataType: "json",
          data: data,
          context: validator.currentForm,
          success: function (response) {
            var valid = response === true || response === "true",
              errors, message, submitted;

            validator.settings.messages[element.name][method] = previous.originalMessage;
            if (valid) {
              submitted = validator.formSubmitted;
              validator.resetInternals();
              validator.toHide = validator.errorsFor(element);
              validator.formSubmitted = submitted;
              validator.successList.push(element);
              validator.invalid[element.name] = false;
              validator.showErrors();
            } else {
              errors = {};
              message = response || validator.defaultMessage(element, {
                method: method,
                parameters: value
              });
              errors[element.name] = previous.message = message;
              validator.invalid[element.name] = true;
              validator.showErrors(errors);
            }
            previous.valid = valid;
            validator.stopRequest(element, valid);
          }
        }, param));
        return "pending";
      }
    }

  });

  // Ajax mode: abort
  // usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
  // if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()

  var pendingRequests = {},
    ajax;

  // Use a prefilter if available (1.5+)
  if ($.ajaxPrefilter) {
    $.ajaxPrefilter(function (settings, _, xhr) {
      var port = settings.port;
      if (settings.mode === "abort") {
        if (pendingRequests[port]) {
          pendingRequests[port].abort();
        }
        pendingRequests[port] = xhr;
      }
    });
  } else {

    // Proxy ajax
    ajax = $.ajax;
    $.ajax = function (settings) {
      var mode = ("mode" in settings ? settings : $.ajaxSettings).mode,
        port = ("port" in settings ? settings : $.ajaxSettings).port;
      if (mode === "abort") {
        if (pendingRequests[port]) {
          pendingRequests[port].abort();
        }
        pendingRequests[port] = ajax.apply(this, arguments);
        return pendingRequests[port];
      }
      return ajax.apply(this, arguments);
    };
  }
  return $;
}));

/* Materializecss Stepper - By Kinark 2016
// https://github.com/Kinark/Materialize-stepper
// JS v2.1.3
*/

var validation = $.isFunction($.fn.valid) ? 1 : 0;

$.fn.isValid = function () {
  if (validation) {
    return this.valid();
  } else {
    return true;
  }
};

if (validation) {
  $.validator.setDefaults({
    errorClass: 'invalid',
    validClass: "valid",
    errorPlacement: function (error, element) {
      if (element.is(':radio') || element.is(':checkbox')) {
        error.insertBefore($(element).parent());
      } else {
        error.insertAfter(element); // default error placement.
        // element.closest('label').data('error', error);
        // element.next().attr('data-error', error);
      }
    },
    success: function (element) {
      if (!$(element).closest('li').find('label.invalid:not(:empty)').length) {
        $(element).closest('li').removeClass('wrong');
      }
    }
  });

  // When parallel stepper is defined we need to consider invisible and
  // hidden fields
  if ($('.stepper.parallel').length) $.validator.setDefaults({
    ignore: ''
  });
}

$.fn.getActiveStep = function () {
  var active = this.find('.step.active');
  return $(this.children('.step:visible')).index($(active)) + 1;
};

$.fn.activateStep = function (callback) {
  if ($(this).hasClass('step')) return;
  var stepper = $(this).closest('ul.stepper');
  stepper.find('>li').removeAttr("data-last");
  if (window.innerWidth < 993 || !stepper.hasClass('horizontal')) {
    $(this).addClass("step").stop().slideDown(400, function () {
      $(this).css({
        'height': 'auto',
        'margin-bottom': '',
        'display': 'inherit'
      });
      if (callback) callback();
      stepper.find('>li.step').last().attr('data-last', 'true');

    });
  } else {
    $(this).addClass("step").stop().css({
      'width': '0%',
      'display': 'inherit'
    }).animate({
      width: '100%'
    }, 400, function () {
      $(this).css({
        'height': 'auto',
        'margin-bottom': '',
        'display': 'inherit'
      });
      if (callback) callback();
      stepper.find('>li.step').last().attr('data-last', 'true');
    });
  }
};

$.fn.deactivateStep = function (callback) {
  if (!$(this).hasClass('step')) return;
  var stepper = $(this).closest('ul.stepper');
  stepper.find('>li').removeAttr("data-last");
  if (window.innerWidth < 993 || !stepper.hasClass('horizontal')) {
    $(this).stop().css({
      'transition': 'none',
      '-webkit-transition': 'margin-bottom none'
    }).slideUp(400, function () {
      $(this).removeClass("step").css({
        'height': 'auto',
        'margin-bottom': '',
        'transition': 'margin-bottom .4s',
        '-webkit-transition': 'margin-bottom .4s'
      });
      if (callback) callback();
      stepper.find('>li').removeAttr("data-last");
      stepper.find('>li.step').last().attr('data-last', 'true');
    });
  } else {
    $(this).stop().animate({
      width: '0%'
    }, 400, function () {
      $(this).removeClass("step").hide().css({
        'height': 'auto',
        'margin-bottom': '',
        'display': 'none',
        'width': ''
      });
      if (callback) callback();
      stepper.find('>li.step').last().attr('data-last', 'true');
    });
  }
};

$.fn.showError = function (error) {
  if (validation) {
    var name = this.attr('name');
    var form = this.closest('form');
    var obj = {};
    obj[name] = error;
    form.validate().showErrors(obj);
    this.closest('li').addClass('wrong');
  } else {
    this.removeClass('valid').addClass('invalid');
    this.next().attr('data-error', error);
  }
};

$.fn.activateFeedback = function () {
  var active = this.find('.step.active:not(.feedbacking)').addClass('feedbacking').find('.step-new-content');
  active.prepend('<div class="wait-feedback"> <div class="preloader-wrapper active"> <div class="spinner-layer spinner-blue"> <div class="circle-clipper left"> <div class="circle"></div></div><div class="gap-patch"> <div class="circle"></div></div><div class="circle-clipper right"> <div class="circle"></div></div></div><div class="spinner-layer spinner-red"> <div class="circle-clipper left"> <div class="circle"></div></div><div class="gap-patch"> <div class="circle"></div></div><div class="circle-clipper right"> <div class="circle"></div></div></div><div class="spinner-layer spinner-yellow"> <div class="circle-clipper left"> <div class="circle"></div></div><div class="gap-patch"> <div class="circle"></div></div><div class="circle-clipper right"> <div class="circle"></div></div></div><div class="spinner-layer spinner-green"> <div class="circle-clipper left"> <div class="circle"></div></div><div class="gap-patch"> <div class="circle"></div></div><div class="circle-clipper right"> <div class="circle"></div></div></div></div></div>');
};

$.fn.destroyFeedback = function () {
  var active = this.find('.step.active.feedbacking');
  if (active) {
    active.removeClass('feedbacking');
    active.find('.wait-feedback').remove();
  }
  return true;
};

$.fn.resetStepper = function (step) {
  if (!step) step = 1;
  var form = $(this).closest('form');
  $(form)[0].reset();
  Materialize.updateTextFields();

  if (validation) {
    form.validate().resetForm();
    $(this).find('li.step').removeClass('wrong');
  }

  return $(this).openStep(step);
};

$.fn.submitStepper = function (step) {
  var form = this.closest('form');
  if (form.isValid()) {
    form.submit();
  }
};

$.fn.nextStep = function (callback, activefb, e) {
  var stepper = this;
  var settings = $(stepper).data('settings');
  var form = this.closest('form');
  var active = this.find('.step.active');
  var next = $(this.children('.step:visible')).index($(active)) + 2;
  var feedback = active.find('.next-step').length > 1 ? (e ? $(e.target).data("feedback") : undefined) : active.find('.next-step').data("feedback");
  // If the stepper is parallel, we want to validate the input of the current active step. Not all elements.
  if ((settings.parallel && $(active).validateStep()) || (!settings.parallel && form.isValid())) {
    if (feedback && activefb) {
      if (settings.showFeedbackLoader) stepper.activateFeedback();
      return window[feedback].call();
    }
    active.removeClass('wrong').addClass('done');
    this.openStep(next, callback);
    return this.trigger('nextstep');
  } else {
    return active.removeClass('done').addClass('wrong');
  }
};

$.fn.prevStep = function (callback) {
  var active = this.find('.step.active');
  if (active.hasClass('feedbacking')) return;
  var prev = $(this.children('.step:visible')).index($(active));
  active.removeClass('wrong');
  this.openStep(prev, callback);
  return this.trigger('prevstep');
};

$.fn.openStep = function (step, callback) {
  var settings = $(this).closest('ul.stepper').data('settings');
  var $this = this;
  var step_num = step - 1;
  step = this.find('.step:visible:eq(' + step_num + ')');
  if (step.hasClass('active')) return;
  var active = this.find('.step.active');
  var next;
  var prev_active = next = $(this.children('.step:visible')).index($(active));
  var order = step_num > prev_active ? 1 : 0;
  if (active.hasClass('feedbacking')) $this.destroyFeedback();
  active.closeAction(order);
  step.openAction(order, function () {
    if (settings.autoFocusInput) step.find('input:enabled:visible:first').focus();
    $this.trigger('stepchange').trigger('step' + (step_num + 1));
    if (step.data('event')) $this.trigger(step.data('event'));
    if (callback) callback();
  });
};

$.fn.closeAction = function (order, callback) {
  var closable = this.removeClass('active').find('.step-new-content');
  if (window.innerWidth < 993 || !this.closest('ul').hasClass('horizontal')) {
    closable.stop().slideUp(300, "easeOutQuad", callback);
  } else {
    if (order == 1) {
      closable.animate({
        left: '-100%'
      }, function () {
        closable.css({
          display: 'none',
          left: '0%'
        }, callback);
      });
    } else {
      closable.animate({
        left: '100%'
      }, function () {
        closable.css({
          display: 'none',
          left: '0%'
        }, callback);
      });
    }
  }

  if (this.closest('ul').hasClass('horizontal') && this.closest('ul').hasClass('horizontal-fix')) {
    var bodyHeight = $('.step-new-content').children().height() + 250;
    $(this).parent().animate({
      height: bodyHeight
    }, "slow")
  }
};

$.fn.openAction = function (order, callback) {
  var openable = this.removeClass('done').addClass('active').find('.step-new-content');
  if (window.innerWidth < 993 || !this.closest('ul').hasClass('horizontal')) {
    openable.slideDown(300, "easeOutQuad", callback);
  } else {
    if (order == 1) {
      openable.css({
        left: '100%',
        display: 'block'
      }).animate({
        left: '0%'
      }, callback);
    } else {
      openable.css({
        left: '-100%',
        display: 'block'
      }).animate({
        left: '0%'
      }, callback);
    }
  }

  if (this.closest('ul').hasClass('horizontal') && this.closest('ul').hasClass('horizontal-fix')) {
    var bodyHeight = $(this).find('.step-new-content').children().height() + 250;
    $(this).parent().animate({
      height: bodyHeight
    }, "fast")
  }
};

$.fn.mdbStepper = function (options) {
  var settings = $.extend({
    linearStepsNavigation: true,
    autoFocusInput: true,
    showFeedbackLoader: true,
    autoFormCreation: true,
    parallel: false // By default we don't assume the stepper is parallel
  }, options);
  $(document).on('click', function (e) {
    if (!$(e.target).parents(".stepper").length) {
      $('.stepper.focused').removeClass('focused');
    }
  });

  $(this).each(function () {
    var $stepper = $(this);
    if (!$stepper.parents("form").length && settings.autoFormCreation) {
      var method = $stepper.data('method');
      var action = $stepper.data('action');
      var method = (method ? method : "GET");
      action = (action ? action : "?");
      $stepper.wrap('<form action="' + action + '" method="' + method + '"></form>');


    }

    $stepper.data('settings', {
      linearStepsNavigation: settings.linearStepsNavigation,
      autoFocusInput: settings.autoFocusInput,
      showFeedbackLoader: settings.showFeedbackLoader,
      parallel: $stepper.hasClass('parallel')
    });
    $stepper.find('li.step.active').openAction(1);
    $stepper.find('>li').removeAttr("data-last");
    $stepper.find('>li.step').last().attr('data-last', 'true');

    $stepper.on("click", '.step:not(.active)', function () {
      var object = $($stepper.children('.step:visible')).index($(this));
      if ($stepper.data('settings').parallel && validation) { // Invoke parallel stepper behaviour
        $(this).addClass('temp-active');
        $stepper.validatePreviousSteps()
        $stepper.openStep(object + 1);
        $(this).removeClass('temp-active');
      } else if (!$stepper.hasClass('linear')) {
        $stepper.openStep(object + 1);
      } else if (settings.linearStepsNavigation) {
        var active = $stepper.find('.step.active');
        if ($($stepper.children('.step:visible')).index($(active)) + 1 == object) {
          $stepper.nextStep(undefined, true, undefined);
        } else if ($($stepper.children('.step:visible')).index($(active)) - 1 == object) {
          $stepper.prevStep(undefined);
        }
      }
    }).on("click", '.next-step', function (e) {
      e.preventDefault();
      $stepper.nextStep(undefined, true, e);
    }).on("click", '.previous-step', function (e) {
      e.preventDefault();
      $stepper.prevStep(undefined);
    }).on("click", "button:submit:not(.next-step, .previous-step)", function (e) {
      e.preventDefault();
      feedback = e ? $(e.target).data("feedback") : undefined;
      var form = $stepper.closest('form');
      if (form.isValid()) {
        if (feedback) {
          stepper.activateFeedback();
          return window[feedback].call();
        }
        form.submit();
      }
    }).on("click", function () {
      $('.stepper.focused').removeClass('focused');
      $(this).addClass('focused');
    });
  });
};

/**
 * Return the step element on given index.
 *
 * @param step, index of the step to be returned
 * @returns {*}, the step requested
 */
$.fn.getStep = function (step) {
  var settings = $(this).closest('ul.stepper').data('settings');
  var $this = this;
  var step_num = step - 1;
  step = this.find('.step:visible:eq(' + step_num + ')');
  return step;
};

/**
 * Run validation over all previous steps from the steps this
 * function is called on.
 */
$.fn.validatePreviousSteps = function () {
  var active = $(this).find('.step.temp-active');
  var index = $(this.children('.step')).index($(active));
  // We assume that the validator is set to ignore nothing.
  $(this.children('.step')).each(function (i) {
    if (i >= index) {
      $(this).removeClass('wrong done');
    } else {
      $(this).validateStep();
    }
  });
};

/**
 * Validate the step that this function is called on.
 */
$.fn.validateStep = function () {
  var stepper = this.closest('ul.stepper');
  var form = this.closest('form');
  var step = $(this);
  // Retrieve the custom validator for that step if exists.
  var validator = step.find('.next-step').data("validator");
  if (this.validateStepInput()) { // If initial base validation succeeded go on
    if (validator) { // If a custom validator is given also call that validator
      if (window[validator].call()) {
        step.removeClass('wrong').addClass('done');
        return true;
      } else {
        step.removeClass('done').addClass('wrong');
        return false;
      }
    }
    step.removeClass('wrong').addClass('done');
    return true;
  } else {
    step.removeClass('done').addClass('wrong');
    return false;
  }
};

/**
 * Uses the validation variable set by the stepper constructor
 * to run standard validation on the current step.
 * @returns {boolean}
 */
$.fn.validateStepInput = function () {
  var valid = true;
  if (validation) {
    // Find all input fields dat need validation in current step.
    $(this).find('input.validate').each(function () {
      if (!$(this).valid()) {
        valid = false;
        return false;
      }
    });
  }
  return valid;
};
